using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
using App.Controlador.Exceptions;
using App.Modelo;
using App.Modelo.Interfaces;
using System.Linq;
using App.Persistencia;
using NHibernate;
using NHibernate.Criterion;

namespace App.Controlador
{
    public class ControladoraUsuarios : ControladoraBase
    {
        private Idioma defaultCulture = new Idioma("es-AR");

        private Grupo _webUserDefaultGroup;
        public ISession _Session;

        public static event EventHandler<UsuarioEventArgs> UsuarioModificado;
        
        public string WebUserDefaultGroupName { get; set; }

        public ControladoraUsuarios()
        {
            WebUserDefaultGroupName = "DefaultWebUser";
        }
        
        public Idioma DefaultCulture
        {
            get { return defaultCulture; }
            set { defaultCulture = value; }
        }

        public void UpdateClaveUsuario(Usuario usuario, string claveVieja, string claveNueva)
        {
            if (!ValidarClaveUsuario(usuario, claveVieja))
                throw new ClaveInvalidaException();

            UpdateClaveUsuario(usuario, claveNueva);
        }

        private bool ValidarNuevaClaveUsuario(string claveNueva)
        {
            return claveNueva.Length >= 3;
        }

        public void UpdateClaveUsuario(IUsuario usuario, string claveNueva)
        {
            var us = Repositorio.GetById<Usuario>(usuario.Id);

            if( us == null )
                throw new UsuarioInexistenteException();
            
            if (!ValidarNuevaClaveUsuario(claveNueva))
                throw new ClaveUsuarioInvalidaException();

            us.Clave = _encriptarClaveUsuario(claveNueva);

            Repositorio.UpdateObject(us);
            Repositorio.FlushSession();
        }

        public bool ValidarClaveUsuario(Usuario usuario, string claveVieja)
        {
            return usuario.Clave == _encriptarClaveUsuario(claveVieja);
        }


        public Usuario Get(string Login)
        {
            return Repositorio.Get<Usuario>("Login", Login);
        }

        public Usuario Get(int id)
        {
            return Repositorio.GetById<Usuario>(id);
        }

        public bool AutenticarUsuario(string username, string password)
        {
            Usuario u = Get(username);

            if (u != null)
            {
                if (ValidarClaveUsuario(u, password))
                {
                    RegistrarAcceso(u);

                    return true;
                }
            }


            return false;
        }


        public Usuario Alta(Usuario usuario)
        {
            if (null != Get(usuario.Login))
                throw new UsuarioExistenteException();

            if (usuario.Grupos.Count == 0)
                throw new GrupoInexistenteException();

            if (usuario.Clave != null && !ValidarNuevaClaveUsuario(usuario.Clave))
                throw new ClaveUsuarioInvalidaException();

            if (!ValidarLoginUsuario(usuario.Login))
                throw new LoginInvalidoException();


            // inicializo la cultura del usuario
            if (usuario.Cultura == null)
                usuario.Cultura = this.DefaultCulture;

            usuario.FechaCreacion = DateTime.Now;

            Repositorio.SaveObject(usuario);

            return usuario;
        }

        public bool ValidarLoginUsuario(string login)
        {
            return login != "";
        }

        public Usuario Get(IUsuario usuario)
        {
            return Get(usuario.Id);
        }

        public IUsuario Alta(string idGrupo, string dni, string apellido, string nombre, string clave, string login)
        {
            Grupo grupo = Repositorio.GetById<App.Modelo.Grupo>(idGrupo);

            return this.Alta(grupo, dni, apellido, nombre, clave, login);
        }

        public Usuario Alta(Grupo grupo, string dni, string apellido, string nombre, string clave, string login)
        {

            return Alta(new Usuario()
                              {
                                  Nombre = nombre,
                                  Apellido = apellido,
                                  DNI = dni,
                                  Clave = _encriptarClaveUsuario(clave),
                                  Login = login,
                                  Grupos = new List<Grupo>() {grupo}
                              });

        }

        public IList<Usuario> List()
        {
            return Repositorio.GetAll<App.Modelo.Usuario>();
        }

        public IList<Usuario> List(string criterio)
        {
            Dictionary<string, object> par = new Dictionary<string, object>();
            par.Add("criterio", "%" + criterio.ToLower().Trim() + "%");

            return Repositorio.GetList<Usuario>(
                "FROM Usuario u WHERE u.Login LIKE :criterio"
                + " OR u.Apellido LIKE :criterio"
                + " OR u.Nombre LIKE :criterio "
                + " OR u.DNI LIKE :criterio "
                , par);
        }


        public void Delete(Usuario usuario)
        {
            Repositorio.DeleteObject(Get(usuario));
            Repositorio.FlushSession();
        }

        public bool Permite(Usuario Usuario, Object Form, String Funcion)
        {
            Type tipo = Form.GetType();
            foreach( var grupo in Usuario.Grupos )
            {
                var param = new Dictionary<string, object>();
                param["Formulario"] = tipo.FullName;
                param["Funcion"] = Funcion;
                param["Grupo"] = grupo.Id;

                var perfiles = Repositorio.GetList<Perfil>( @" FROM Perfil p WHERE 
                        p.Formulario.Nombre = :Formulario
                    AND p.Funcion.Nombre    = :Funcion 
                    AND p.Grupo.Id          = :Grupo",
                        param
                        );

                if(perfiles.Count == 1)
                {
                    return true;
                }
            }
            return false;

        }


        public string[] GetPrivilegios(string username)
        {
            return GetPrivilegios(Get(username));
        }

        public string[] GetPrivilegios(Usuario Usuario)
        {   
            var perfiles = new List<string>();

            foreach (var grupo in Usuario.Grupos)
            {
                var param = new Dictionary<string, object>();
                param["Grupo"] = grupo.Id;

                var gp = Repositorio.GetList<Perfil>(
                    "FROM Perfil p WHERE p.Grupo.Id = :Grupo", param
                );

                perfiles.AddRange(from p in perfiles select p.ToString());

            }
            return perfiles.Distinct().ToArray();
        }

        public void EnsureWebUserDefaultGroupIsCreated()
        {
            if (_webUserDefaultGroup == null)
            {
                Configuracion config = Repositorio.GetById<Configuracion>("webUserDefaultGroup");

                if (config != null)
                    _webUserDefaultGroup = Repositorio.GetById<Grupo>(Int32.Parse(config.Valor));

                if (config == null || _webUserDefaultGroup == null)
                {
                    _webUserDefaultGroup = new Grupo(WebUserDefaultGroupName);

                    Repositorio.SaveObject(_webUserDefaultGroup);

                    if (config == null)
                        config = new Configuracion("webUserDefaultGroup");

                    config.Valor = _webUserDefaultGroup.Id.ToString();

                    Repositorio.SaveObject(config);

                    Repositorio.FlushSession();
                }

                IList<Grupo> d = Repositorio.GetAll<Grupo>();
            }
        }

        public Grupo WebUserDefaultGroup
        {
            get
            {
                EnsureWebUserDefaultGroupIsCreated();
                return _webUserDefaultGroup;
            }
        }



        public IList<Grupo> GetGruposAsignables()
        {
            return Repositorio.GetAll<Grupo>();
        }

        public IUsuario Alta(IUsuario iUsuario, string password, Grupo grupo)
        {
            Usuario user = new Usuario
                               {
                                   Nombre = iUsuario.Nombre,
                                   Apellido = iUsuario.Apellido,
                                   Email = iUsuario.Email,
                                   Login = iUsuario.Login,
                                   Clave = _encriptarClaveUsuario(password)
                               };
            user.Grupos.Add(  Repositorio.GetById<Grupo>(grupo.Id) );
            return this.Alta(user);
        }

        public IUsuario Update(IUsuario iUsuario, IGrupo grupo)
        {
            Usuario Usuario = Get(iUsuario);
            Usuario.Nombre = iUsuario.Nombre;
            Usuario.Apellido = iUsuario.Apellido;
            Usuario.Login = iUsuario.Login;
            Usuario.Email = iUsuario.Email;

            if( ! Usuario.Grupos.Any(gr => gr.Equals(grupo)))
            {
                Usuario.Grupos.Add(Repositorio.GetById<Grupo>(grupo.Id));
            }
            

            // notifico el cambio en el usuario
            if (UsuarioModificado != null)
                UsuarioModificado(this, new UsuarioEventArgs(Usuario));


            return Update(Usuario);
        }

        public IUsuario Update(Usuario usuario)
        {
            // el usuario puede modificar su login, siempre que este sea unico
            IUsuario old = this.Get(usuario);
            if (usuario ==null || !usuario.Equals(old))
                throw new UsuarioInexistenteException();

            if (usuario.Grupos.Count==0)
                throw new GrupoInexistenteException();

            if (!ValidarLoginUsuario(usuario.Login))
                throw new LoginInvalidoException();

            Repositorio.UpdateObject(usuario);
            Repositorio.FlushSession();

            if (UsuarioModificado != null)
                UsuarioModificado(this, new UsuarioEventArgs(usuario));

            return usuario;
        }


        public void RegistrarAcceso(Usuario usuario)
        {
            // actualizo la ultima fecha del login
            var accesoPrevio = _obtenerUltimoAcceso(usuario);

            if (accesoPrevio != null && accesoPrevio.Fecha != null)
            {
                usuario.FechaUltimoLogin = accesoPrevio.Fecha.Value;
                Repositorio.UpdateObject(usuario);
            }

            // registro el nuevo acceso
            var acceso = new UsuarioAcceso
                             {
                                 Usuario = usuario,
                                 Fecha = DateTime.Now
                             };

            Repositorio.SaveObject(acceso);
        }

        private static string _encriptarClaveUsuario(string str)
        {
            MD5 md5 = MD5CryptoServiceProvider.Create();
            ASCIIEncoding encoding = new ASCIIEncoding();
            byte[] stream = null;
            StringBuilder sb = new StringBuilder();
            stream = md5.ComputeHash(encoding.GetBytes(str));
            for (int i = 0; i < stream.Length; i++) sb.AppendFormat("{0:x2}", stream[i]);
            return sb.ToString();
        }

        public object GetSessionId()
        {
            return Repositorio.GetSessionId();
        }

        public void Dispose()
        {
        }

        public void Logout(IUsuario Usuario)
        {
            var usuario = (Usuario) Usuario;
            // actualizo la ultima fecha del login

            var accesoPrevio = _obtenerUltimoAcceso(usuario);
                
            if (accesoPrevio == null)
            {
                accesoPrevio = new UsuarioAcceso(usuario);
                accesoPrevio.Fecha = DateTime.Now;
            }

            accesoPrevio.FechaLogout = DateTime.Now;

            Repositorio.SaveObject(accesoPrevio);
            Repositorio.FlushSession();
        }

        private UsuarioAcceso _obtenerUltimoAcceso(Usuario usuario)
        {
            var accesoPrevio = ((Repositorio)Repositorio).CreateCriteria<UsuarioAcceso>()
                .Add(new SimpleExpression("Usuario", usuario, "="))
                .AddOrder(new Order("Fecha", false))
                .SetMaxResults(1)
                .List<UsuarioAcceso>()
                .FirstOrDefault();

            return accesoPrevio;
        }
    }
}